#include <zbar.h>
#include <opencv2/opencv.hpp>
#include "GSLAM/core/GSLAM.h"

using namespace std;
using namespace cv;
using namespace zbar;

class Scanner{
public:
    Scanner(){
        scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
    }

    sv::Svar scan(sv::SvarBuffer buf){
        if(buf.shape.size()!=2 && buf._format!=buf.format<uchar>()){
            LOG(FATAL)<<"Unsupported format.";
        }

        Image image(buf.shape[1], buf.shape[0], "Y800", buf.ptr<uchar>(), buf.size());

        // Scan the image for barcodes and QRCodes
        int n = scanner.scan(image);

        std::vector<sv::Svar> markers;
        // Print results
        for(Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol)
        {
           sv::Svar marker;
           marker["type"]=symbol->get_type_name();
           marker["name"]=symbol->get_data();
           std::vector<sv::Svar> locations;

           for(int i = 0; i< symbol->get_location_size(); i++)
           {
              locations.push_back({symbol->get_location_x(i),symbol->get_location_y(i)});
           }

           marker["locations"]= locations;
           markers.push_back(marker);
        }
        return markers;
    }

    cv::Mat display(std::string name,cv::Mat im,std::vector<sv::Svar> ret){
        cv::Mat mat;
        cv::cvtColor(im,mat,CV_GRAY2BGR);
        // Loop over all decoded objects
        for(int i = 0; i < ret.size(); i++)
        {
          vector<cv::Point> points = ret[i]["locations"].castAs<vector<cv::Point>>();
          vector<Scalar>    colors = {Scalar(255,0,0),Scalar(0,255,0),Scalar(0,0,255),Scalar(255,255,255)};
          for(int j = 0; j < points.size(); j++)
          {
            line(mat, points[j], points[ (j+1) % points.size()], colors[j], 3);
          }
        }

        return mat;
    }

    ImageScanner scanner;
};

REGISTER_SVAR_MODULE(zbar){
    sv::Class<::Scanner>("Scanner")
            .construct<>()
            .def("scan",&::Scanner::scan)
            .def("display",&::Scanner::display);

    sv::Class<cv::Mat>("Mat")
                .construct<>()
                .def("empty",&cv::Mat::empty)
                .def_readonly("data",&cv::Mat::data)
                .def("elemSize",&cv::Mat::elemSize)
                .def("elemSize1",&cv::Mat::elemSize1)
                .def("channels",&cv::Mat::channels)
                .def("type",&cv::Mat::type)
                .def("clone",&cv::Mat::clone)
                .def("__repr__",[](const cv::Mat& img){
                    return to_string(img.cols)+"x"
                            +to_string(img.rows)+"x"+to_string(img.channels());})
                .def("__init__",[](sv::SvarBuffer buffer){
            if(buffer._holder.is<cv::Mat>())
                return buffer._holder.as<cv::Mat>();
            std::string format=buffer._format;
            std::vector<ssize_t> shape=buffer.shape;
            int channels=1;
            if(shape.size()==2)
                channels=1;
            else if(shape.size()==3)
                channels=shape[2];
            else
                LOG(FATAL)<<"Shape should be 2 or 3 demisson";

            int rows=shape[0];
            int cols=shape[1];

            static int lut[256]={0};
            lut['B']=0;lut['b']=1;
            lut['H']=2;lut['h']=3;
            lut['i']=4;lut['f']=5;
            lut['d']=6;
            int type=lut[format.front()]+((channels-1)<<3);
            return cv::Mat(rows,cols,type,(uchar*)buffer.ptr()).clone();
        })
        .def("__buffer__",[](cv::Mat& self){
            std::string formats[]={"B","b","H","h","i","f","d"};
            std::string format   =formats[(self.type()&0x7)];
            return sv::SvarBuffer(self.data,self.elemSize1(),format,{self.rows,self.cols,self.channels()},{},self);
        })
        ;

    sv::Class<cv::Point>("Point")
                .def("__init__",[](std::vector<double> xy){
            return cv::Point(xy[0],xy[1]);
        })
        ;

}

EXPORT_SVAR_INSTANCE
